package com.flejay.dreamrecruiter.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.lang3.StringUtils;

import com.flejay.dreamrecruiter.enums.BlocksTransfomerCaseEnum;
import com.flejay.dreamrecruiter.enums.SubBlocksTransfomerCaseEnum;
import com.flejay.dreamrecruiter.enums.BlocksTransfomerCaseEnum.WhatToDo;
import com.flejay.dreamrecruiter.enums.SubBlocksTransfomerCaseEnum.SecondLevelSplitting;
import com.flejay.dreamrecruiter.model.AbstractBlock;
import com.flejay.dreamrecruiter.model.AbstractTag;
import com.flejay.dreamrecruiter.model.Block;
import com.flejay.dreamrecruiter.model.SubBlock;
import com.flejay.dreamrecruiter.model.SubTag;
import com.flejay.dreamrecruiter.pojo.Bag;
import com.flejay.dreamrecruiter.service.ICleanerService;
import com.flejay.dreamrecruiter.service.ILoggingService;
import com.flejay.dreamrecruiter.service.IParsingEngineService;
import com.flejay.dreamrecruiter.service.ITransformerService;
import com.flejay.dreamrecruiter.tools.BlockTool;
import com.flejay.dreamrecruiter.tools.CustomStringUtils;
import com.flejay.dreamrecruiter.tools.IConstantsUtil;
import com.flejay.dreamrecruiter.tools.TagTool;

@Singleton
@Named("transformerService")
public class TransformerService implements ITransformerService {

	@Inject
	@Named("cleanerService")
	private ICleanerService cleanerService;
	
	@Inject
	@Named("parsingEngineService")
	private IParsingEngineService parsingEngineService;
	
	@Inject
	@Named("logService")
	private ILoggingService logService;
	
	@Override
	public Bag extractSubBlocksFromLinesMatching(final String[] lines, List<SubTag> subTags){
		List<SubBlock> subBlocks = new ArrayList<SubBlock>();
		List<String> resContent = new ArrayList<String>();
		// s'il n'y a aucun critere alors on retourne une liste vide et le texte plein
		if (subTags.isEmpty()) {
			resContent = Arrays.asList(lines.clone());
		}
		else {
			SubTag previousSubTag = null;
			SubBlock previousSubBlock = null;
			//traitement particulier dans le cas de detection d'un tag mission
			Boolean ignoreDateTag = false;
			for(String line : lines){
				SubTag subTagMatching = (SubTag)parsingEngineService.getFirstFoundTag(line, AbstractTag.toAbstractTagListFromSubTags(subTags) );
				SecondLevelSplitting decideWhatToDo = decideWhatDoToNext(subTagMatching,previousSubTag);
				switch (decideWhatToDo) {
					case LINE_MATCHES:
						SubBlocksTransfomerCaseEnum.WhatToDo decide = detectSpecificMissionTag(subTagMatching,ignoreDateTag);
						switch (decide) {
							case REPLACE_PREVIOUS_SUB_BLOCK_BY_NEW:
								ignoreDateTag = true;
								previousSubBlock = createSubBlock(line, subTagMatching);
								subBlocks.add(previousSubBlock);
							break;
							case REPLACE_CURRENT_SUB_BLOCK_BY_PREVIOUS:
								subBlocks.add(createSubBlock(line, subTagMatching));
								subTagMatching = previousSubTag;
							break;
							case IGNORE:
								ignoreDateTag = false;
							break;
							case DO_NOTHING: break;
						}
						previousSubBlock = chooseWheterContinueOrStayToActualTagAndBlock(subBlocks, previousSubBlock, ignoreDateTag, line, subTagMatching);
					break;
					case LINE_DOES_NOT_MATCHES:
						resContent.add(line);
					break;
					case LINE_DOES_NOT_MATCHES_AND_PREVIOUS_DOES:
						previousSubBlock.getContent().append(IConstantsUtil.LINE_SEPARATOR).append(line);	
					break;
					case NOT_HANDLED: throw new IllegalStateException("Not handled decision. Internal error");
				}
				if (subTagMatching != null){
					previousSubTag = subTagMatching;
				}
			}
		}
		return Bag.getNewInstance(new Object[]{subBlocks,CustomStringUtils.convert(resContent)});
	}

	/**
	 * Decide si on dois rester au tag actuel ou si on bouge au suivant
	 * 
	 * @param subBlocks
	 * @param previousSubBlock
	 * @param ignoreDateTag
	 * @param line
	 * @param subTagMatching
	 * @return
	 */
	private SubBlock chooseWheterContinueOrStayToActualTagAndBlock(List<SubBlock> subBlocks,SubBlock previousSubBlock, Boolean ignoreDateTag, String line,
			SubTag subTagMatching) {
		if (!ignoreDateTag){
			if (subTagMatching.isAgglomerate()){
				SubBlock subBlock = BlockTool.retrieveSubBlock(subBlocks,subTagMatching);
				if (subBlock == null){
					previousSubBlock = createSubBlock(line, subTagMatching);
					subBlocks.add(previousSubBlock);	
				} else {
					subBlock.getContent().append(IConstantsUtil.LINE_SEPARATOR).append(line);	
					previousSubBlock = subBlock;
				}
			} else {
				previousSubBlock = createSubBlock(line, subTagMatching);
				subBlocks.add(previousSubBlock);
			}
		}
		return previousSubBlock;
	}
	
	/**
	 * decide what to do
	 * 
	 * @param subTagMatching
	 * @param previousSubTag
	 * @return
	 */
	private SecondLevelSplitting decideWhatDoToNext(SubTag subTagMatching,SubTag previousSubTag) {
		SecondLevelSplitting res = SecondLevelSplitting.NOT_HANDLED;
		if (subTagMatching != null){
			res = SecondLevelSplitting.LINE_MATCHES;
		} else {
			if (previousSubTag == null){
				res = SecondLevelSplitting.LINE_DOES_NOT_MATCHES;
			} else {
				if (previousSubTag.isIsolatedStrategy()){
					res = SecondLevelSplitting.LINE_DOES_NOT_MATCHES;
				} else {
					res = SecondLevelSplitting.LINE_DOES_NOT_MATCHES_AND_PREVIOUS_DOES;
				}
			}
		}
		return res;
	}

	/**
	 * Traitement particulier pour le tag mission : on cherche a rattacher les tags dates qui suivent
	 * 
	 * @param subTagMatching
	 * @param ignoreDateTagParam
	 * @return
	 */
	private SubBlocksTransfomerCaseEnum.WhatToDo detectSpecificMissionTag(SubTag subTagMatching, Boolean ignoreDateTagParam) {
		SubBlocksTransfomerCaseEnum.WhatToDo res = SubBlocksTransfomerCaseEnum.WhatToDo.DO_NOTHING;
		if("mission".equalsIgnoreCase(subTagMatching.getLabel())){
			res = SubBlocksTransfomerCaseEnum.WhatToDo.REPLACE_PREVIOUS_SUB_BLOCK_BY_NEW;
		} else if ("date".equalsIgnoreCase(subTagMatching.getLabel())){
			if (ignoreDateTagParam){
				res = SubBlocksTransfomerCaseEnum.WhatToDo.REPLACE_CURRENT_SUB_BLOCK_BY_PREVIOUS;
			}
		} else {
			res = SubBlocksTransfomerCaseEnum.WhatToDo.IGNORE;
		}
		return res;
	}

	/**
	 * Create a new block
	 * 
	 * @param line
	 * @param subTagMatching
	 * @return
	 */
	private SubBlock createSubBlock(String line, SubTag subTagMatching) {
		SubBlock subBlock = SubBlock.getNewInstance();
		subBlock.getContent().append(line);
		subBlock.setAssociatedTag(subTagMatching);
		return subBlock;
	}
	
	/**
	 * Extrait la liste de block qui matche avec les tags
	 * 
	 * @param lines
	 * @param tags
	 * @return
	 */
	@Override
	public List<AbstractBlock> extractBlocksFromLinesMatching(final String[] lines, final List<AbstractTag> tags) {
		this.logService.debug(this.getClass(), "\n.firstTransformCV > lines={},tags={}", new Object[]{lines,tags});
		List<AbstractBlock> blocks = new ArrayList<AbstractBlock>();
		// s'il n'y a aucun critere alors on retourne une liste vide
		if (tags.isEmpty()) {
			return blocks;
		}
		List<AbstractTag> tagsRemaining = TagTool.copyList(tags);
		AbstractTag currentTagInAssociation = null;
		AbstractBlock block = null;
		for(String line : lines) {
			if (block == null){
				block = AbstractTag.getNewInstanceTyped(tags.get(0));
			}
			List<String> preparedLineList = cleanerService.prepareLine(line);
			for (String preparedLine : preparedLineList){
				if (!StringUtils.isEmpty(preparedLine)){
					AbstractTag taggingMatch = calculateTaggingMatch(currentTagInAssociation,preparedLine,tagsRemaining);
					WhatToDo decision = calculateWhatToDo(taggingMatch,currentTagInAssociation);
					switch (decision) {
						case GET_FIRST_TAG:
							currentTagInAssociation = tagsRemaining.get(0);
							tagsRemaining.remove(currentTagInAssociation);
							genericfillBlock(block,preparedLine);
							block.setAssociatedTag((AbstractTag)currentTagInAssociation);
						break;
						case KEEP_FILLING_BLOCK:
							genericfillBlock(block,preparedLine);
						break;
						case MOVE_TO_NEXT_TAG:
							tagsRemaining.remove(currentTagInAssociation);
							currentTagInAssociation = taggingMatch;
							blocks.add(block);
							block = AbstractTag.getNewInstanceTyped(tags.get(0));
							block.setAssociatedTag(currentTagInAssociation);
							tagsRemaining.remove(currentTagInAssociation);
							genericfillBlock(block,preparedLine);
						break;
						case NOT_HANDLED: throw new IllegalStateException("Not handled decision. Internal error");
					}
				}
			}
		}
		if(!blocks.contains(block)){
			blocks.add(block);
		}
		this.logService.debug(this.getClass(), "\n.firstTransformCV < blocks={}", blocks);
		return blocks;
	}
	
	/**
	 * Return the tag who matches the line. If not any matches then return null
	 * 
	 * @param currentTag
	 * @param preparedLine
	 * @param tagsRemaining
	 * @return
	 */
	private AbstractTag calculateTaggingMatch(AbstractTag currentTag,String preparedLine, List<AbstractTag> tagsRemaining) {
		AbstractTag taggingMatch = null;
		if (currentTag != null){
			taggingMatch = parsingEngineService.getFirstFoundTag(preparedLine, tagsRemaining);
		}
		return taggingMatch;
	}

	/**
	 * Append content into the block
	 * 
	 * @param blockToFill
	 * @param content
	 */
	private void genericfillBlock(final AbstractBlock blockToFill,String content){
		String backline = "\n";
		blockToFill.getContent()
			.append(content)
			.append(backline);
	}
	
	/**
	 * Decide what to do depending on params
	 * 
	 * @param taggingMatch
	 * @param currentTag
	 * @return
	 */
	private WhatToDo calculateWhatToDo(AbstractTag taggingMatch, AbstractTag currentTag) {
		BlocksTransfomerCaseEnum.WhatToDo res = WhatToDo.NOT_HANDLED;
		if (taggingMatch == null){
			if (currentTag == null){
				res = WhatToDo.GET_FIRST_TAG;
			} else {
				res = WhatToDo.KEEP_FILLING_BLOCK;
			}
		} else {
			if (currentTag == null){
				res = WhatToDo.KEEP_FILLING_BLOCK;
			} else if (taggingMatch.equals(currentTag)){
				res = WhatToDo.KEEP_FILLING_BLOCK;
			} else {
				res = WhatToDo.MOVE_TO_NEXT_TAG;
			}
		}
		return res;
	}

	@Override
	public List<AbstractBlock> extractOverSizedBlocks(final List<Block> blockList) {
		List<AbstractBlock> overSizedBlockList = new ArrayList<AbstractBlock>();
		for(Block block : blockList){
			if (block.isOverSized()){
				overSizedBlockList.add(block);
			}
		}
		return overSizedBlockList;
	}
}